package com.googlecode.geoteacher.logic;

import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.apache.commons.codec.binary.Hex;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.maps.android.PolyUtil;
import com.googlecode.geoteacher.util.JsonUtils;

public class ChallengeFileReader {

	private static final String DIGEST_ALGORITHM = "SHA-256";

	private static final String JSON_VERSION = "version";
	private static final String JSON_LAT = "lat";
	private static final String JSON_LNG = "lng";
	private static final String JSON_SOUTHWEST = "sw";
	private static final String JSON_NORTHEAST = "ne";
	private static final String JSON_BOUNDS = "bounds";
	private static final String JSON_PLACES = "places";
	private static final String JSON_NAME = "name";
	private static final String JSON_CHALLENGE = "challenge";
	private static final String JSON_BORDER = "border";
	private static final String JSON_DISPLAY = "display";
	private static final String JSON_LINE = "line";

	public static ChallengeFile read(InputStream in) throws IOException,
			JSONException, NoSuchAlgorithmException {

		// init hash generation
		MessageDigest digest = MessageDigest.getInstance(DIGEST_ALGORITHM);

		JSONObject json = JsonUtils.loadObject(in, digest);

		if (json.getInt(JSON_VERSION) != 1) {
			throw new IllegalStateException(
					"cannot parse challenge file with version: "
							+ json.getInt(JSON_VERSION));
		}

		String hash = Hex.encodeHexString(digest.digest());

		ChallengeFile file = parseFile(json, hash);

		return file;
	}

	private static ChallengeFile parseFile(JSONObject obj, String hash)
			throws JSONException {

		List<Place> placesList = new ArrayList<Place>();

		JSONArray places = obj.getJSONArray(JSON_PLACES);
		for (int i = 0; i < places.length(); i++) {
			placesList.add(parsePlace(places.getJSONObject(i)));
		}

		return new ChallengeFile(obj.getString(JSON_NAME),
				parseBounds(obj.getJSONObject(JSON_BOUNDS)), placesList, hash);
	}

	private static LatLngBounds parseBounds(JSONObject obj)
			throws JSONException {

		return new LatLngBounds(parseLatLng(obj.getJSONObject(JSON_SOUTHWEST)),
				parseLatLng(obj.getJSONObject(JSON_NORTHEAST)));
	}

	private static LatLng parseLatLng(JSONObject obj) throws JSONException {
		return new LatLng(obj.getDouble(JSON_LAT), obj.getDouble(JSON_LNG));
	}

	private static Place parsePlace(JSONObject obj) throws JSONException {

		String name = obj.getString(JSON_NAME);
		Place place = null;

		if (obj.has(JSON_BORDER)) {
			place = new Place.Area(name,
					parseBorder(obj.getJSONArray(JSON_BORDER)));
		} else if (obj.has(JSON_LINE)) {
			place = new Place.Line(name, PolyUtil.decode(obj
					.getString(JSON_LINE)));
		} else if (obj.has(JSON_LAT) && obj.has(JSON_LNG)) {
			place = new Place.Point(name, parseLatLng(obj));
		} else {
			throw new IllegalStateException(
					"Place is not parseable as point/line/area: " + name);
		}

		if (obj.has(JSON_CHALLENGE)) {
			place.setChallenge(obj.getBoolean(JSON_CHALLENGE));
		}

		if (obj.has(JSON_DISPLAY)) {
			place.setDisplay(obj.getBoolean(JSON_DISPLAY));
		}

		return place;
	}

	private static List<List<LatLng>> parseBorder(JSONArray arr)
			throws JSONException {
		List<List<LatLng>> border = new ArrayList<List<LatLng>>();

		for (int i = 0, len = arr.length(); i < len; ++i) {
			border.add(PolyUtil.decode(arr.getString(i)));
		}

		return border;
	}
}